home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / Console / Table.php < prev   
Encoding:
PHP Script  |  2007-12-20  |  20.2 KB  |  663 lines

  1. <?php
  2. // +-----------------------------------------------------------------------+
  3. // | Copyright (c) 2002-2003 Richard Heyes                                 |
  4. // | All rights reserved.                                                  |
  5. // |                                                                       |
  6. // | Redistribution and use in source and binary forms, with or without    |
  7. // | modification, are permitted provided that the following conditions    |
  8. // | are met:                                                              |
  9. // |                                                                       |
  10. // | o Redistributions of source code must retain the above copyright      |
  11. // |   notice, this list of conditions and the following disclaimer.       |
  12. // | o Redistributions in binary form must reproduce the above copyright   |
  13. // |   notice, this list of conditions and the following disclaimer in the |
  14. // |   documentation and/or other materials provided with the distribution.|
  15. // | o The names of the authors may not be used to endorse or promote      |
  16. // |   products derived from this software without specific prior written  |
  17. // |   permission.                                                         |
  18. // |                                                                       |
  19. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
  20. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
  21. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  22. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
  23. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  24. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
  25. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  26. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  27. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
  28. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  29. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
  30. // |                                                                       |
  31. // +-----------------------------------------------------------------------+
  32. // | Author: Richard Heyes <richard@phpguru.org>                           |
  33. // |         Jan Schneider <jan@horde.org>                                 |
  34. // +-----------------------------------------------------------------------+
  35. //
  36. // $Id: Table.php,v 1.19 2007/01/19 22:30:07 yunosh Exp $
  37. //
  38. // Utility for printing tables from cmdline scripts
  39. //
  40.  
  41. define('CONSOLE_TABLE_HORIZONTAL_RULE', 1);
  42. define('CONSOLE_TABLE_ALIGN_LEFT', -1);
  43. define('CONSOLE_TABLE_ALIGN_CENTER', 0);
  44. define('CONSOLE_TABLE_ALIGN_RIGHT', 1);
  45.  
  46. class Console_Table
  47. {
  48.     /**
  49.      * The table headers.
  50.      *
  51.      * @var array
  52.      */
  53.     var $_headers = array();
  54.  
  55.     /**
  56.      * The data of the table.
  57.      *
  58.      * @var array
  59.      */
  60.     var $_data = array();
  61.  
  62.     /**
  63.      * The max number of columns in a row.
  64.      *
  65.      * @var integer
  66.      */
  67.     var $_max_cols = 0;
  68.  
  69.     /**
  70.      * The max number of rows in the table.
  71.      *
  72.      * @var integer
  73.      */
  74.     var $_max_rows = 0;
  75.  
  76.     /**
  77.      * Lengths of the columns, calculated when rows are added to the table.
  78.      *
  79.      * @var array
  80.      */
  81.     var $_cell_lengths = array();
  82.  
  83.     /**
  84.      * Heights of the rows.
  85.      *
  86.      * @var array
  87.      */
  88.     var $_row_heights = array();
  89.  
  90.     /**
  91.      * How many spaces to use to pad the table.
  92.      *
  93.      * @var integer
  94.      */
  95.     var $_padding = 1;
  96.  
  97.     /**
  98.      * Column filters.
  99.      *
  100.      * @var array
  101.      */
  102.     var $_filters = array();
  103.  
  104.     /**
  105.      * Columns to calculate totals for.
  106.      *
  107.      * @var array
  108.      */
  109.     var $_calculateTotals;
  110.  
  111.     /**
  112.      * Alignment of the columns.
  113.      *
  114.      * @var array
  115.      */
  116.     var $_col_align = array();
  117.  
  118.     /**
  119.      * Default alignment of columns.
  120.      *
  121.      * @var integer
  122.      */
  123.     var $_defaultAlign;
  124.  
  125.     /**
  126.      * Charset of the data.
  127.      *
  128.      * @var string
  129.      */
  130.     var $_charset = 'utf-8';
  131.  
  132.     /**
  133.      * Constructor.
  134.      *
  135.      * @param integer $align  Default alignment
  136.      */
  137.     function Console_Table($align = CONSOLE_TABLE_ALIGN_LEFT)
  138.     {
  139.         $this->_defaultAlign = $align;
  140.     }
  141.  
  142.     /**
  143.      * Converts an array to a table. Must be 
  144.      *
  145.      * @static
  146.      *
  147.      * @param array $headers         Headers for the table.
  148.      * @param array $data            A two dimensional array with the table
  149.      *                               data.
  150.      * @param boolean $returnObject  Whether to return the Console_Table object
  151.      *                               instead of the rendered table.
  152.      *
  153.      * @return Console_Table|string  A Console_Table object or the generated
  154.      *                               table.
  155.      */
  156.     function fromArray($headers, $data, $returnObject = false)
  157.     {
  158.         if (!is_array($headers) || !is_array($data)) {
  159.             return false;
  160.         }
  161.  
  162.         $table = &new Console_Table();
  163.         $table->setHeaders($headers);
  164.  
  165.         foreach ($data as $row) {
  166.             $table->addRow($row);
  167.         }
  168.  
  169.         return $returnObject ? $table : $table->getTable();
  170.     }
  171.  
  172.     /**
  173.      * Adds a filter to a column.
  174.      *
  175.      * Filters are standard PHP callbacks which are run on the data before
  176.      * table generation is performed. Filters are applied in the order they
  177.      * are added. The callback function must accept a single argument, which
  178.      * is a single table cell.
  179.      *
  180.      * @param integer $col     Column to apply filter to.
  181.      * @param mixed $callback  PHP callback to apply.
  182.      */
  183.     function addFilter($col, &$callback)
  184.     {
  185.         $this->_filters[] = array($col, &$callback);
  186.     }
  187.  
  188.     /**
  189.      * Sets the charset of the provided table data.
  190.      *
  191.      * @string $charset  A charset supported by the mbstring PHP extension.
  192.      */
  193.     function setCharset($charset)
  194.     {
  195.         $this->_charset = strtolower($charset);
  196.     }
  197.  
  198.     /**
  199.      * Sets the alignment for the columns.
  200.      *
  201.      * @param integer $col_id  The column number.
  202.      * @param integer $align   Alignment to set for this column. One of
  203.      *                         CONSOLE_TABLE_ALIGN_LEFT
  204.      *                         CONSOLE_TABLE_ALIGN_CENTER
  205.      *                         CONSOLE_TABLE_ALIGN_RIGHT.
  206.      */
  207.     function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT)
  208.     {
  209.         switch ($align) {
  210.             case CONSOLE_TABLE_ALIGN_CENTER:
  211.                 $pad = STR_PAD_BOTH;
  212.                 break;
  213.             case CONSOLE_TABLE_ALIGN_RIGHT:
  214.                 $pad = STR_PAD_LEFT;
  215.                 break;
  216.             default:
  217.                 $pad = STR_PAD_RIGHT;
  218.                 break;
  219.         }
  220.         $this->_col_align[$col_id] = $pad;
  221.     }
  222.  
  223.     /**
  224.      * Specifies which columns are to have totals calculated for them and
  225.      * added as a new row at the bottom.
  226.      *
  227.      * @param array $cols  Array of column numbers (starting with 0).
  228.      */
  229.     function calculateTotalsFor($cols)
  230.     {
  231.         $this->_calculateTotals = $cols;
  232.     }
  233.  
  234.     /**
  235.      * Sets the headers for the columns.
  236.      *
  237.      * @param array $headers  The column headers.
  238.      */
  239.     function setHeaders($headers)
  240.     {
  241.         $this->_headers = array(array_values($headers));
  242.         $this->_updateRowsCols($headers);
  243.     }
  244.  
  245.     /**
  246.      * Adds a row to the table.
  247.      *
  248.      * @param array $row       The row data to add.
  249.      * @param boolean $append  Whether to append or prepend the row.
  250.     */
  251.     function addRow($row, $append = true)
  252.     {
  253.         if ($append) {
  254.             $this->_data[] = array_values($row);
  255.         } else {
  256.             array_unshift($this->_data, array_values($row));
  257.         }
  258.  
  259.         $this->_updateRowsCols($row);
  260.     }
  261.  
  262.     /**
  263.      * Inserts a row after a given row number in the table.
  264.      *
  265.      * If $row_id is not given it will prepend the row.
  266.      *
  267.      * @param array $row       The data to insert.
  268.      * @param integer $row_id  Row number to insert before.
  269.      */
  270.     function insertRow($row, $row_id = 0)
  271.     {
  272.         array_splice($this->_data, $row_id, 0, array($row));
  273.  
  274.         $this->_updateRowsCols($row);
  275.     }
  276.  
  277.     /**
  278.      * Adds a column to the table.
  279.      *
  280.      * @param array $col_data  The data of the column.
  281.      * @param integer $col_id  The column index to populate.
  282.      * @param integer $row_id  If starting row is not zero, specify it here.
  283.      */
  284.     function addCol($col_data, $col_id = 0, $row_id = 0)
  285.     {
  286.         foreach ($col_data as $col_cell) {
  287.             $this->_data[$row_id++][$col_id] = $col_cell;
  288.         }
  289.  
  290.         $this->_updateRowsCols();
  291.         $this->_max_cols = max($this->_max_cols, $col_id + 1);
  292.     }
  293.  
  294.     /**
  295.      * Adds data to the table.
  296.      *
  297.      * @param array $data      A two dimensional array with the table data.
  298.      * @param integer $col_id  Starting column number.
  299.      * @param integer $row_id  Starting row number.
  300.      */
  301.     function addData($data, $col_id = 0, $row_id = 0)
  302.     {
  303.         foreach ($data as $row) {
  304.             if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
  305.                 $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE;
  306.                 $row_id++;
  307.                 continue;
  308.             }
  309.             $starting_col = $col_id;
  310.             foreach ($row as $cell) {
  311.                 $this->_data[$row_id][$starting_col++] = $cell;
  312.             }
  313.             $this->_updateRowsCols();
  314.             $this->_max_cols = max($this->_max_cols, $starting_col);
  315.             $row_id++;
  316.         }
  317.     }
  318.  
  319.     /**
  320.      * Adds a horizontal seperator to the table.
  321.      */
  322.     function addSeparator()
  323.     {
  324.         $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE;
  325.     }
  326.  
  327.     /*
  328.      * Returns the table in wonderful ASCII art.
  329.      *
  330.      * @return string  The generated table.
  331.      */
  332.     function getTable()
  333.     {
  334.         $this->_applyFilters();
  335.         $this->_calculateTotals();
  336.         $this->_validateTable();
  337.  
  338.         return $this->_buildTable();
  339.     }
  340.  
  341.     /**
  342.      * Calculates totals for columns.
  343.      */
  344.     function _calculateTotals()
  345.     {
  346.         if (!empty($this->_calculateTotals)) {
  347.             $this->addSeparator();
  348.  
  349.             $totals = array();
  350.             foreach ($this->_data as $row) {
  351.                 if (is_array($row)) {
  352.                     foreach ($this->_calculateTotals as $columnID) {
  353.                         $totals[$columnID] += $row[$columnID];
  354.                     }
  355.                 }
  356.             }
  357.  
  358.             $this->_data[] = $totals;
  359.             $this->_updateRowsCols();
  360.         }
  361.     }
  362.  
  363.     /**
  364.      * Applies any column filters to the data.
  365.      */
  366.     function _applyFilters()
  367.     {
  368.         if (!empty($this->_filters)) {
  369.             foreach ($this->_filters as $filter) {
  370.                 $column   = $filter[0];
  371.                 $callback = $filter[1];
  372.  
  373.                 foreach ($this->_data as $row_id => $row_data) {
  374.                     if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  375.                         $this->_data[$row_id][$column] = call_user_func($callback, $row_data[$column]);
  376.                     }
  377.                 }
  378.             }
  379.         }
  380.     }
  381.  
  382.     /**
  383.      * Ensures column and row counts are correct.
  384.      */
  385.     function _validateTable()
  386.     {
  387.         $this->_calculateRowHeight(-1, $this->_headers[0]);
  388.  
  389.         for ($i = 0; $i < $this->_max_rows; $i++) {
  390.             for ($j = 0; $j < $this->_max_cols; $j++) {
  391.                 if (!isset($this->_data[$i][$j]) &&
  392.                     (!isset($this->_data[$i]) ||
  393.                      $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) {
  394.                     $this->_data[$i][$j] = '';
  395.                 }
  396.  
  397.             }
  398.             $this->_calculateRowHeight($i, $this->_data[$i]);
  399.  
  400.             if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  401.                  ksort($this->_data[$i]);
  402.             }
  403.  
  404.         }
  405.  
  406.         $this->_splitMultilineRows();
  407.  
  408.         // Update cell lengths.
  409.         for ($i = 0; $i < count($this->_headers); $i++) {
  410.             $this->_calculateCellLengths($this->_headers[$i]);
  411.         }
  412.         for ($i = 0; $i < $this->_max_rows; $i++) {
  413.             $this->_calculateCellLengths($this->_data[$i]);
  414.         }
  415.  
  416.         ksort($this->_data);
  417.     }
  418.  
  419.     /**
  420.      * Splits multiline rows into many smaller one-line rows.
  421.      */
  422.     function _splitMultilineRows()
  423.     {
  424.         ksort($this->_data);
  425.         $sections = array(&$this->_headers, &$this->_data);
  426.         $max_rows = array(count($this->_headers), $this->_max_rows);
  427.  
  428.         for ($s = 0; $s <= 1; $s++) {
  429.             $inserted = 0;
  430.             $new_data = $sections[$s];
  431.  
  432.             for ($i = 0; $i < $max_rows[$s]; $i++) {
  433.                 // Process only rows that have many lines.
  434.                 if (($height = $this->_row_heights[$i]) > 1) {
  435.                     // Split column data into one-liners.
  436.                     $split = array();
  437.                     for ($j = 0; $j < $this->_max_cols; $j++) {
  438.                         $split[$j] = preg_split('/\r?\n|\r/', $sections[$s][$i][$j]);
  439.                     }
  440.  
  441.                     $new_rows = array();
  442.                     // Construct new 'virtual' rows - insert empty strings for
  443.                     // columns that have less lines that the highest one.
  444.                     for ($i2 = 0; $i2 < $height; $i2++) {
  445.                         for ($j = 0; $j < $this->_max_cols; $j++) {
  446.                             $new_rows[$i2][$j] = !empty($split[$j][$i2]) ? $split[$j][$i2] : '';
  447.                         }
  448.                     }
  449.  
  450.                     // Replace current row with smaller rows.  $inserted is
  451.                     // used to take account of bigger array because of already
  452.                     // inserted rows.
  453.                     array_splice($new_data, $i + $inserted, 1, $new_rows);
  454.                     $inserted += count($new_rows) - 1;
  455.                 }
  456.             }
  457.  
  458.             // Has the data been modified?
  459.             if ($inserted > 0) {
  460.                 $sections[$s] = $new_data;
  461.                 $this->_updateRowsCols();
  462.             }
  463.         }
  464.     }
  465.  
  466.     /**
  467.      * Builds the table.
  468.      */
  469.     function _buildTable()
  470.     {
  471.         $return = array();
  472.         for ($i = 0; $i < count($this->_data); $i++) {
  473.             for ($j = 0; $j < count($this->_data[$i]); $j++) {
  474.                 if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE &&
  475.                     $this->_strlen($this->_data[$i][$j]) < $this->_cell_lengths[$j]) {
  476.                     $this->_data[$i][$j] = str_pad($this->_data[$i][$j],
  477.                                             $this->_cell_lengths[$j],
  478.                                             ' ',
  479.                                             $this->_col_align[$j]);
  480.                 }
  481.             }
  482.  
  483.             if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  484.                 $row_begin    = '|' . str_repeat(' ', $this->_padding);
  485.                 $row_end      = str_repeat(' ', $this->_padding) . '|';
  486.                 $implode_char = str_repeat(' ', $this->_padding) . '|' .
  487.                     str_repeat(' ', $this->_padding);
  488.                 $return[] = $row_begin . implode($implode_char, $this->_data[$i]) .
  489.                     $row_end;
  490.             } else {
  491.                 $return[] = $this->_getSeparator();
  492.             }
  493.  
  494.         }
  495.  
  496.         $return = $this->_getSeparator() . "\r\n" . implode("\n", $return) .
  497.             "\r\n" . $this->_getSeparator() . "\r\n";
  498.  
  499.         if (!empty($this->_headers)) {
  500.             $return = $this->_getHeaderLine() .  "\r\n" . $return;
  501.         }
  502.  
  503.         return $return;
  504.     }
  505.  
  506.     /**
  507.      * Creates a horizontal separator for header separation and table
  508.      * start/end etc.
  509.      */
  510.     function _getSeparator()
  511.     {
  512.         foreach ($this->_cell_lengths as $cl) {
  513.             $return[] = str_repeat('-', $cl);
  514.         }
  515.  
  516.         $row_begin    = '+' . str_repeat('-', $this->_padding);
  517.         $row_end      = str_repeat('-', $this->_padding) . '+';
  518.         $implode_char = str_repeat('-', $this->_padding) . '+' .
  519.             str_repeat('-', $this->_padding);
  520.  
  521.         return $row_begin . implode($implode_char, $return) . $row_end;
  522.     }
  523.  
  524.     /**
  525.      * Returns header line for the table.
  526.      */
  527.     function _getHeaderLine()
  528.     {
  529.         // Make sure column count is correct
  530.         for ($j = 0; $j < count($this->_headers); $j++) {
  531.             for ($i = 0; $i < $this->_max_cols; $i++) {
  532.                 if (!isset($this->_headers[$j][$i])) {
  533.                     $this->_headers[$j][$i] = '';
  534.                 }
  535.             }
  536.         }
  537.  
  538.         for ($j = 0; $j < count($this->_headers); $j++) {
  539.             for ($i = 0; $i < count($this->_headers[$j]); $i++) {
  540.                 if ($this->_strlen($this->_headers[$j][$i]) < $this->_cell_lengths[$i]) {
  541.                     $this->_headers[$j][$i] = str_pad($this->_headers[$j][$i],
  542.                                                       $this->_cell_lengths[$i],
  543.                                                       ' ',
  544.                                                       $this->_col_align[$i]);
  545.                 }
  546.             }
  547.         }
  548.  
  549.         $row_begin    = '|' . str_repeat(' ', $this->_padding);
  550.         $row_end      = str_repeat(' ', $this->_padding) . '|';
  551.         $implode_char = str_repeat(' ', $this->_padding) . '|' .
  552.             str_repeat(' ', $this->_padding);
  553.  
  554.         $return[] = $this->_getSeparator();
  555.         for ($j = 0; $j < count($this->_headers); $j++) {
  556.             $return[] = $row_begin .
  557.                 implode($implode_char, $this->_headers[$j]) .
  558.                 $row_end;
  559.         }
  560.  
  561.         return implode("\r\n", $return);
  562.     }
  563.  
  564.     /**
  565.      * Update max cols/rows.
  566.      */
  567.     function _updateRowsCols($rowdata = null)
  568.     {
  569.         // Update max cols
  570.         $this->_max_cols = max($this->_max_cols, count($rowdata));
  571.  
  572.         // Update max rows
  573.         ksort($this->_data);
  574.         $keys = array_keys($this->_data);
  575.         $this->_max_rows = end($keys) + 1;
  576.  
  577.         switch ($this->_defaultAlign) {
  578.             case CONSOLE_TABLE_ALIGN_CENTER: $pad = STR_PAD_BOTH; break;
  579.             case CONSOLE_TABLE_ALIGN_RIGHT:  $pad = STR_PAD_LEFT; break;
  580.             default:                         $pad = STR_PAD_RIGHT; break;
  581.         }
  582.  
  583.         // Set default column alignments
  584.         for ($i = count($this->_col_align); $i < $this->_max_cols; $i++) {
  585.             $this->_col_align[$i] = $pad;
  586.         }
  587.     }
  588.  
  589.     /**
  590.      * This function given a row of data will calculate the max length for
  591.      * each column and store it in the _cell_lengths array.
  592.      *
  593.      * @param array $row  The row data.
  594.      */
  595.     function _calculateCellLengths($row)
  596.     {
  597.         for ($i = 0; $i < count($row); $i++) {
  598.             if (!isset($this->_cell_lengths[$i])) {
  599.                 $this->_cell_lengths[$i] = 0;
  600.             }
  601.             $this->_cell_lengths[$i] = max($this->_cell_lengths[$i],
  602.                                            $this->_strlen($row[$i]));
  603.         }
  604.     }
  605.  
  606.     /**
  607.      * This function given a row of data will calculate the max height for all
  608.      * columns and store it in the _row_heights array.
  609.      *
  610.      * @param integer $row_number  The row number.
  611.      * @param array $row           The row data.
  612.      */
  613.     function _calculateRowHeight($row_number, $row)
  614.     {
  615.         if (!isset($this->_row_heights[$row_number])) {
  616.             $this->_row_heights[$row_number] = 1;
  617.         }
  618.  
  619.         // Do not process horizontal rule rows.
  620.         if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
  621.             return;
  622.         }
  623.  
  624.         for ($i = 0; $i < count($row); $i++) {
  625.             $lines = preg_split('/\r?\n|\r/', $row[$i]);
  626.             $this->_row_heights[$row_number] = max($this->_row_heights[$row_number],
  627.                                                    count($lines));
  628.         }
  629.     }
  630.  
  631.     /**
  632.      * Returns the character length of a string.
  633.      *
  634.      * @param string $str  A multibyte or singlebyte string.
  635.      *
  636.      * @return integer  The string length.
  637.      */
  638.     function _strlen($str)
  639.     {
  640.         static $mbstring, $utf8;
  641.  
  642.         // Cache expensive function_exists() calls.
  643.         if (!isset($mbstring)) {
  644.             $mbstring = function_exists('mb_strlen');
  645.         }
  646.         if (!isset($utf8)) {
  647.             $utf8 = function_exists('utf8_decode');
  648.         }
  649.  
  650.         if ($utf8 &&
  651.             ($this->_charset == strtolower('utf-8') ||
  652.              $this->_charset == strtolower('utf8'))) {
  653.             return strlen(utf8_decode($str));
  654.         }
  655.         if ($mbstring) {
  656.             return mb_strlen($str, $this->_charset);
  657.         }
  658.  
  659.         return strlen($str);
  660.     }
  661.  
  662. }
  663.